using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using Kaleida.ServiceMonitor.Model.Runtime;

namespace Kaleida.ServiceMonitor.Model
{
    public class ServiceMonApplication
    {
        private readonly MinuteTimer minuteTimer = new MinuteTimer();

        private readonly ScriptWorkspace workspace = new ScriptWorkspace();
        private readonly List<Monitor> monitors = new List<Monitor>();
        private readonly IConfiguration configuration;
        [CanBeNull] private ScriptCompilation currentCompilation;

        internal event EventHandler<ScriptCompilationCompleteEventArgs> ScriptCompilationComplete;

        public ServiceMonApplication(IConfiguration configuration)
        {
            this.configuration = configuration;

            workspace.CurrentScriptChanged += (s, e) => TryCompileCurrentScript();
            workspace.CurrentScriptContentChanged += (s, e) => TryCompileCurrentScript();

            minuteTimer.Tick += OnMinuteTimerTick;
            minuteTimer.Start();
        }

        public static Version VersionNumber
        {
            get
            {
                var assembly = Assembly.GetExecutingAssembly();
                return assembly.GetName().Version;
            }
        }

        public IConfiguration Configuration
        {
            get { return configuration; }
        }

        public ScriptWorkspace Workspace
        {
            get { return workspace; }
        }

        public bool HasSuccessfullyCompiledScript
        {
            get { return currentCompilation != null && currentCompilation.Succeeded; }
        }

        public bool HasScriptError
        {
            get { return currentCompilation != null && !currentCompilation.Succeeded; }
        }

        [CanBeNull]
        public ScriptCompilation CurrentCompilation
        {
            get { return currentCompilation; }
        }

        public string ScriptName
        {
            get
            {
                return currentCompilation == null
                           ? "<None>"
                           : currentCompilation.Succeeded
                                 ? currentCompilation.CompiledScript.Name
                                 : "<!>";
            }
        }

        public void SendMessageToAllMonitors(Action<Monitor> action)
        {
            foreach (var monitor in Monitors)
            {
                action(monitor);
            }
        }

        public IList<Monitor> Monitors
        {
            get
            {
                lock (monitors)
                {
                    return monitors.ToList().AsReadOnly();
                }
            }
        }

        private void TryCompileCurrentScript()
        {
            var scriptSource = workspace.CurrentScript;
            currentCompilation = scriptSource.Compile();

            lock (monitors)
            {
                ClearExistingMonitors();

                if (currentCompilation.Succeeded)
                {
                    monitors.AddRange(currentCompilation.CompiledScript.AllScriptModules.Select(BuildMonitor));
                }
                SendMessageToAllMonitors(i => i.State.ScriptCompiled(currentCompilation));
            }

            if (ScriptCompilationComplete != null)
                ScriptCompilationComplete(this, new ScriptCompilationCompleteEventArgs(currentCompilation));
        }

        private void ClearExistingMonitors()
        {
            foreach(var monitor in monitors)
            {
                monitor.Dispose();
            }

            monitors.Clear();
        }

        private Monitor BuildMonitor(CompiledScriptModule compiledScriptModule)
        {
            var monitor = new Monitor(this, compiledScriptModule);
            monitor.State.MonitorStarted += OnStartedMonitoring;
            monitor.State.MonitorStopped += OnStoppedMonitoring;
            return monitor;
        }

        private void OnStartedMonitoring(object sender, EventArgs args)
        {
            var monitorState = (MonitorState) sender;
            monitorState.Monitor.SendMessageToExtensions(i => i.OnStartedMonitoring());
        }

        private void OnStoppedMonitoring(object sender, EventArgs args)
        {
            var monitorState = (MonitorState) sender;
            monitorState.Monitor.SendMessageToExtensions(i => i.OnStoppedMonitoring());
        }

        private void OnMinuteTimerTick(object o, MinuteTimerTickEventArgs e)
        {
            lock (monitors)
            {
                SendMessageToAllMonitors(i => i.SendMessageToExtensions(j => j.OnScheduledTimeTick(e)));
            }
        }

        public Monitor TryGetMonitor(CompiledScriptModule scriptModule)
        {
            lock (monitors)
            {
                return monitors.FirstOrDefault(i => i.CompiledModule == scriptModule);
            }
        }
    }
}